﻿using BitConverter;
using JianZhangTool.log;
using JianZhangTool.tool;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace JianZhangTool.net
{
    class BiliDanmuConnecterTCP
    {
        /// <summary>
        /// 包参数magic
        /// </summary>
        private const short bili_magic = 16;
        /// <summary>
        /// 包参数ver
        /// </summary>
        private const short bili_protocol_ver = 1;
        /// <summary>
        /// 包参数param
        /// </summary>
        private const int bili_param = 1;
        /// <summary>
        /// 获取房间信息的字符串
        /// </summary>
        private const string danmu_room_info_url = "https://api.live.bilibili.com/room/v1/Danmu/getConf?room_id=";
        /// <summary>
        /// 默认的host
        /// </summary>
        private static string[] default_hosts = new string[] { "livecmt-2.bilibili.com", "livecmt-1.bilibili.com" };

        /// <summary>
        /// http客户端
        /// </summary>
        private HttpClient http_client = new HttpClient();

        /// <summary>
        /// tcp客户端
        /// </summary>
        private TcpClient client = null;
        /// <summary>
        /// 读取流
        /// </summary>
        private Stream net_stream;

        /// <summary>
        /// IP
        /// </summary>
        private string host;
        /// <summary>
        /// 端口号
        /// </summary>
        private int port = 2243;
        /// <summary>
        /// 房间号
        /// </summary>
        private int roomId;
        /// <summary>
        /// Token
        /// </summary>
        private string token;

        /// <summary>
        /// 是否连接中
        /// </summary>
        private bool connected = false;

        public BiliDanmuConnecterTCP(int roomId)
        {
            this.roomId = roomId;
        }

        /// <summary>
        /// 连接
        /// </summary>
        /// <returns></returns>
        public async Task<bool> connect()
        {
            if (connected)
            {
                return false;
            }
            // 获取房间信息
            try
            {
                string req = await http_client.GetStringAsync(danmu_room_info_url + roomId);
                JObject roomobj = JObject.Parse(req);
                token = roomobj["data"]["token"] + "";
                host = roomobj["data"]["host"] + "";
                port = roomobj["data"]["port"].Value<int>();
                if (string.IsNullOrEmpty(host))
                {
                    throw new Exception();
                }
            }
            catch (WebException ex)
            {
                host = default_hosts[new Random().Next(default_hosts.Length)];
                HttpWebResponse errorResponse = ex.Response as HttpWebResponse;
                if (errorResponse.StatusCode == HttpStatusCode.NotFound)
                {
                    // 直播间不存在（HTTP 404）
                    string msg = "[error]该直播间疑似不存在!";
                    Main.Instance.add_log(msg);
                    return false;
                }
                else
                {
                    // B站服务器响应错误
                    string msg = "[error]B站服务器响应弹幕服务器地址出错，尝试使用常见地址连接";
                    Main.Instance.add_log(msg);
                }
            }
            catch (Exception)
            {
                // 其他错误（XML解析错误？）
                host = default_hosts[new Random().Next(default_hosts.Length)];
                string msg = "[error]获取弹幕服务器地址时出现未知错误，尝试使用常见地址连接";
                Main.Instance.add_log(msg);
            }

            client = new TcpClient();
            IPAddress[] ipAddress = await Dns.GetHostAddressesAsync(host);
            Random random = new Random();
            int idx = random.Next(ipAddress.Length);
            await client.ConnectAsync(ipAddress[idx], port);

            net_stream = Stream.Synchronized(client.GetStream());


            if (await join_chat_channel())
            {
                connected = true;
                _ = heart_beat_loop();
                _ = receive_msg_loop();
                return true;
            }
            return false;
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        /// <param name="inside"></param>
        /// <returns></returns>
        public async Task disconnect(bool inside = false)
        {
            if (connected)
            {
                connected = false;
                client.Close();
                net_stream = null;
                // 反馈给主面板
                await Main.Instance.trigger_danmu_disconnect(inside);
            }
        }

        /// <summary>
        /// 请求加入弹幕频道
        /// </summary>
        /// <returns></returns>
        private async Task<bool> join_chat_channel()
        {
            JObject jo = new JObject();
            jo["uid"] = 0;
            jo["roomid"] = roomId;
            jo["protover"] = 3;
            jo["platform"] = "danmuji";
            jo["token"] = token;
            await send_socket_data_async(7, jo.ToString());
            return true;
        }

        /// <summary>
        /// 接收包
        /// </summary>
        /// <returns></returns>
        private async Task receive_msg_loop()
        {
            try
            {
                byte[] stableBuffer = new byte[16];
                byte[] buffer = new byte[4096];
                while (connected)
                {
                    await net_stream.ReadBAsync(stableBuffer, 0, 16);
                    BiliDanmuPacket protocol = BiliDanmuPacket.FromBuffer(stableBuffer);
                    if (protocol.length < 16)
                    {
                        throw new NotSupportedException("协议失败: (L:" + protocol.length + ")");
                    }
                    var payloadlength = protocol.length - 16;
                    if (payloadlength == 0)
                    {
                        continue; // 没有内容了
                    }
                    buffer = new byte[payloadlength];

                    await net_stream.ReadBAsync(buffer, 0, payloadlength);
                    if (protocol.version == 2 && protocol.action == 5)
                    {
                        // 处理deflate消息
                        using (MemoryStream ms = new MemoryStream(buffer, 2, payloadlength - 2)) // Skip 0x78 0xDA
                        using (DeflateStream deflate = new DeflateStream(ms, CompressionMode.Decompress))
                        {
                            var headerbuffer = new byte[16];
                            try
                            {
                                while (true)
                                {
                                    await deflate.ReadBAsync(headerbuffer, 0, 16);
                                    BiliDanmuPacket protocol_in = BiliDanmuPacket.FromBuffer(headerbuffer);
                                    payloadlength = protocol_in.length - 16;
                                    byte[] danmakubuffer = new byte[payloadlength];
                                    await deflate.ReadBAsync(danmakubuffer, 0, payloadlength);
                                    receive_danmu_message(protocol.action, danmakubuffer);
                                }
                            }
                            catch (Exception e)
                            {
                                Logger.log("error", e.StackTrace);
                            }
                        }
                    }
                    else if (protocol.version == 3 && protocol.action == 5) // brotli?
                    {
                        using (var ms = new MemoryStream(buffer)) // Skip 0x78 0xDA
                        using (var deflate = new Brotli.BrotliStream(ms, CompressionMode.Decompress))
                        {
                            var headerbuffer = new byte[16];
                            try
                            {
                                while (true)
                                {
                                    await deflate.ReadBAsync(headerbuffer, 0, 16);
                                    BiliDanmuPacket protocol_in = BiliDanmuPacket.FromBuffer(headerbuffer);
                                    payloadlength = protocol_in.length - 16;
                                    byte[] danmakubuffer = new byte[payloadlength];
                                    await deflate.ReadBAsync(danmakubuffer, 0, payloadlength);
                                    receive_danmu_message(protocol.action, danmakubuffer);
                                }

                            }
                            catch (Exception)
                            {
                                
                            }
                        }
                    }
                    else
                    {
                        receive_danmu_message(protocol.action, buffer);
                    }
                }
            }
            catch (Exception e)
            {
                Logger.log("error", e.StackTrace);
                await disconnect(true);
            }
        }

        /// <summary>
        /// 心跳包
        /// </summary>
        /// <returns></returns>
        private async Task heart_beat_loop()
        {
            try
            {
                while (connected)
                {
                    await send_socket_data_async(2, "");
                    await Task.Delay(30000);
                }
            }
            catch (Exception)
            {
                await disconnect(true);
            }
        }

        /// <summary>
        /// 收到直播间弹幕信息
        /// </summary>
        /// <param name="action"></param>
        /// <param name="buffer"></param>
        private bool receive_danmu_message(int action, byte[] msg_bytes)
        {
            switch (action)
            {
                case 5:
                    {
                        string json_str = Encoding.UTF8.GetString(msg_bytes);
                        JObject bili_msg_json;
                        try
                        {
                            bili_msg_json = JObject.Parse(json_str);
                        }
                        catch (Exception)
                        {
                            return false;
                        }
                        if (bili_msg_json != null)
                        {
                            BiliDanmuMessage.OnMsg(bili_msg_json);
                        }
                        break;
                    }
                default:
                    {
                        break;
                    }
            }
            return true;
        }

        /// <summary>
        /// 发送信息
        /// </summary>
        /// <param name="action"></param>
        /// <param name="body"></param>
        /// <returns></returns>
        private async Task send_socket_data_async(int action, string body = "")
        {
            byte[] body_bytes = Encoding.UTF8.GetBytes(body);
            // 数据总长
            int length = body_bytes.Length + 16;
            // 消息体
            byte[] buffer = new byte[length];
            // 写入
            using (MemoryStream ms = new MemoryStream(buffer))
            {
                var b = EndianBitConverter.BigEndian.GetBytes(buffer.Length);
                await ms.WriteAsync(b, 0, 4);
                b = EndianBitConverter.BigEndian.GetBytes(bili_magic);
                await ms.WriteAsync(b, 0, 2);
                b = EndianBitConverter.BigEndian.GetBytes(bili_protocol_ver);
                await ms.WriteAsync(b, 0, 2);
                b = EndianBitConverter.BigEndian.GetBytes(action);
                await ms.WriteAsync(b, 0, 4);
                b = EndianBitConverter.BigEndian.GetBytes(bili_param);
                await ms.WriteAsync(b, 0, 4);
                if (body_bytes.Length > 0)
                {
                    await ms.WriteAsync(body_bytes, 0, body_bytes.Length);
                }
                await net_stream.WriteAsync(buffer, 0, buffer.Length);
            }
        }
    }
}
